/*                                    _
 *   ___ _ __ ___ _ __ ___   ___   __| |
 *  / __| '__/ __| '_ ` _ \ / _ \ / _` |
 *  \__ \ |  \__ \ | | | | | (_) | (_| |
 *  |___/_|  |___/_| |_| |_|\___/ \__,_|                                  
 *
 *  ._|_ _  _ _    _ _  _ _  _    _ |
 *  | | (/_| | |  | (/_| | |(_)\/(_||
 *         _ _  _  _|   | _ 
 *        | | |(_)(_||_||(/_                               
 *
 ******************************************
 ******************************************
 ******************************************
 * . _ _  _ | _  _ _  _  _ _|_ _ _|_. _  _
 * || | ||_)|(/_| | |(/_| | | (_| | |(_)| |
 *       |                                 
 * 
 *  Fully dynamic system utilizing a KeyValue file for maximum customization:
 *	requires file "sourcemod/configs/srsmod.cfg"
 * 
 *  See that file for further information
 * 
 *
 */

#define MODULE_NAME "ItemRemoval"

#define INTEGER_STRINGLENGTH						   3
#define	NUMBER_OF_ITEM_CLASSES						  64
#define ITEMCLASS_STRINGLENGTH						  64
#define WEAPON_MODEL_STRINGLENGTH					 128

static const Float: INDEX_RESPAWN_DELAY				= 1.5;
static const Float:	CHECKPOINT_ITEMWIPE_DISTANCE	= 1100.0;
static const		SAFEROOM_ITEMS_OF_A_KIND		= 4;

static const 		SPECIAL_OPTION_NO_VALUE			= 0;
static const 		SPECIAL_OPTION_YES				= 1;
static const 		SPECIAL_OPTION_NO				= 2;

static const String:CONFIG_FILE_PATH[]				= "configs/srsmod.cfg";
static const String:CONFIG_CLASSNAME[]				= "classname";
static const String:CONFIG_WEAPON_ID[]				= "weapon_id";
static const String:CONFIG_REMOVAL_FACTOR[]			= "removal_factor";
static const String:CONFIG_REMOVAL_CVAR[]			= "convarremove";
static const String:CONFIG_REMOVAL_LIMIT[]			= "removal_limit";
static const String:CONFIG_REMOVAL_LIMIT_CVAR[]		= "convarlimit";
static const String:CONFIG_REPLACE_FACTOR[]			= "replace_factor";
static const String:CONFIG_REPLACE_CVAR[]			= "convarreplace";
static const String:CONFIG_REPLACE_CLASSNAME[]		= "replace_classname";
static const String:CONFIG_REPLACE_CLASS_CVAR[]		= "convarreplaceclass";
static const String:CONFIG_REPLACE_WEAPON_ID[]		= "replace_id";
static const String:CONFIG_REPLACE_WEAPON_ID_CVAR[]	= "convarreplaceid";
static const String:CONFIG_REPLACE_MODEL[]			= "replace_model";
static const String:CONFIG_REPLACE_MODEL_CVAR[]		= "convarreplacemodel";
static const String:CONFIG_ONLY_AROUND_SPAWN[]		= "aroundspawnpoint";
static const String:CONFIG_AMOUNT_TO_SPAWN[]		= "amounttospawn";
static const String:CONFIG_FINALE_ONLY[]			= "finale_only";
static const String:CONFIG_SPECIAL_OPTION_YES[]		= "yes";
static const String:CONFIG_SPECIAL_OPTION_NO[]		= "no";
static const String:ITEMS_HANDLED_FORWARD_STRING[]	= "SRS_OnItemsHandled";

static const String:ENTITY_KEYVALUE_COUNT[]			= "count";
static const String:ENTITY_COUNT_DEFAULT[]			= "1";
static const String:ENTITY_SPAWN_WEAPON[]			= "weapon_spawn";
static const String:VECTOR_ORIGIN_ENTPROP[]			= "m_vecOrigin";
static const String:ANGLE_ROTATION_ENTPROP[]		= "m_angRotation";
static const String:WEAPON_ID_ENTPROP[]				= "m_weaponID";

static String:configFile[PLATFORM_MAX_PATH]			= "";
static Handle:keyValueHolder						= INVALID_HANDLE;
static Handle:cvarRemoveItems						= INVALID_HANDLE;
static Handle:weaponDataTrie						= INVALID_HANDLE;
static bool:haveItemsBeenIndexed					= false;
static bool:haveItemsBeenRemoved					= false;
static bool:eventsHooked							= false;
static Float:survivorPos[3];

enum itemData
{
	String: className[ITEMCLASS_STRINGLENGTH],
	Handle: convarRemovalFactor,
	Float:  removalFactor,
	Handle: convarReplaceFactor,
			removalLimit,
	Handle:	convarRemovalLimit,
	Float:  replaceFactor,
	String: classNameReplacement[ITEMCLASS_STRINGLENGTH],
	Handle: convarReplaceClass,
			weaponIDReplacement,
	Handle: convarWeaponIDReplacement,
	String: weaponModelReplacement[WEAPON_MODEL_STRINGLENGTH],
	Handle: convarWeaponModelReplacement,
			aroundSpawn,
	String:	amountToReplace[INTEGER_STRINGLENGTH],
			finaleOnly
}

static dataArray[NUMBER_OF_ITEM_CLASSES][itemData];
static Float:positionArray[NUMBER_OF_ITEM_CLASSES][NUMBER_OF_ITEM_CLASSES][3];
static Float:anglesArray[NUMBER_OF_ITEM_CLASSES][NUMBER_OF_ITEM_CLASSES][3];
static bool:replaceItem[NUMBER_OF_ITEM_CLASSES][NUMBER_OF_ITEM_CLASSES];

new Handle:_IR_ItemsHandled_Forward 			= INVALID_HANDLE;

public ItemRemoval_OnModuleLoaded()
{
	cvarRemoveItems =		CreateConVar("srs_remove_enabled", 			"1", 	"Enable the Removal Module.",		SRS_CVAR_DEFAULT_FLAGS);

	_IR_OnModuleEnabled(); // default ON
	HookConVarChange(cvarRemoveItems, _IR_FlipActivation);
	
	_IR_ItemsHandled_Forward = CreateGlobalForward(ITEMS_HANDLED_FORWARD_STRING, ET_Ignore, Param_Cell);
	
	LoadKeyValues();
}

public _IR_FlipActivation(Handle:convar, const String:oldValue[], const String:newValue[])
{
	if (ACTIVATION_FLIP_OFF_TO_ON)
	{
		_IR_OnModuleEnabled();
	}
	else if (ACTIVATION_FLIP_ON_TO_OFF)
	{
		_IR_OnModuleDisabled();
	}
}

static _IR_OnModuleEnabled()
{
	HookEvent("round_start", 				_IR_RoundStart_Event, 		EventHookMode_PostNoCopy);
	HookEvent("round_end", 					_IR_RoundEnd_Event, 		EventHookMode_PostNoCopy);
	HookEvent("player_entered_checkpoint", 	_IR_PlayerEnteredCP_Event, 	EventHookMode_PostNoCopy);
	HookEvent("player_first_spawn", 		_IR_PlayerFirstSpawn_Event, EventHookMode_PostNoCopy);
	eventsHooked = true;
	
	haveItemsBeenRemoved = false;
	haveItemsBeenIndexed = false;
}

static _IR_OnModuleDisabled()
{
	if (!IsPluginEnding() && eventsHooked)
	{
		UnhookEvent("round_start", 					_IR_RoundStart_Event, 		EventHookMode_PostNoCopy);
		UnhookEvent("round_end", 					_IR_RoundEnd_Event, 		EventHookMode_PostNoCopy);
		UnhookEvent("player_entered_checkpoint", 	_IR_PlayerEnteredCP_Event, 	EventHookMode_PostNoCopy);
		UnhookEvent("player_first_spawn", 			_IR_PlayerFirstSpawn_Event, EventHookMode_PostNoCopy);
		eventsHooked = false;
	}
}

public Action:_IR_RoundStart_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	Debug_Print("  IR_RoundStart  ");
	if (RoundStatus_IsFirstRound())
	{
		haveItemsBeenRemoved = false;
		haveItemsBeenIndexed = false;
	}
	else
	{
		RemoveBannedItems();
	}
}

public Action:_IR_RoundEnd_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	Debug_Print("  IR_RoundEnd  ");
	haveItemsBeenRemoved = true;
}

public Action:_IR_PlayerEnteredCP_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	Debug_Print(" IR_PlayerEnterCheckpoint  ");
	if (!haveItemsBeenRemoved)
	{
		RemoveBannedItems();
	}
}

public Action:_IR_PlayerFirstSpawn_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	Debug_Print(" IR_PlayerFirstSpawn  ");
	if (!haveItemsBeenRemoved)
	{
		RemoveBannedItems();
	}
}

static RemoveBannedItems()
{
	if (!IsAllowedGameMode()) return;
	
	new bool:freshIndex = false;

	if (!haveItemsBeenIndexed)
	{
		GetAnySurvivorStartingPosition(survivorPos);
		IndexItems();
		haveItemsBeenIndexed = true;
		freshIndex = true;
	}
	else
	{
		WipeAllItems();
		CreateTimer(INDEX_RESPAWN_DELAY, _IR_SpawnIndexedItems_Timer);
	}
	
	HandleDynamicWeaponSpawns();
	
	haveItemsBeenRemoved = true;
	
	Call_OnItemsHandled_Forward(freshIndex);
}

static Call_OnItemsHandled_Forward(bool:justIndexed)
{
	Call_StartForward(_IR_ItemsHandled_Forward);
	Call_PushCell(justIndexed);
	Call_Finish();
}

static IndexItems()
{
	new bool:scoringEnabled = GetConVarBool(FindConVar("srs_scoring_enabled"));
	decl Float:saferoomorigin[3];
	new bool:endSaferoomExists = GetSafeRoomPosition(saferoomorigin);
	new bool:isFinale = L4D2_IsFinaleMapNow();

	new i = 0;
	new j = 0;
	new Handle:adtItems = INVALID_HANDLE;
	new Handle:itemIndexes = INVALID_HANDLE;
	
	new bool:nearSpawn = false;
	new bool:nearFinish = false;
	new Float:lowestItemDistance = 0.0;
	decl Float:tempcompare;
	
	new ent = INVALID_ENTITY;
	new prev = 0;
	new Float:removalfactorcounter = FACTOR_NONE;
	new Float:replacefactorcounter = FACTOR_NONE;
	new counter = 0;
	
	while (!StrEqual(dataArray[i][className], ""))
	{
		if ((isFinale && dataArray[i][finaleOnly] == SPECIAL_OPTION_NO)
		|| (!isFinale && dataArray[i][finaleOnly] == SPECIAL_OPTION_YES))
		{
			Debug_Print("Map Finale status %b doesnt match with dataset %i, entity %s setting (%b), continuing", isFinale, i, dataArray[i][className], dataArray[i][finaleOnly]);
			i++;
			continue;
		}
		
		Debug_Print("  ---Indexing %s now ---  ", dataArray[i][className]);
		
		if (dataArray[i][convarRemovalFactor] != INVALID_HANDLE)
		{
			dataArray[i][removalFactor] = GetConVarFloat(dataArray[i][convarRemovalFactor]);
		}
		if (dataArray[i][convarReplaceFactor] != INVALID_HANDLE)
		{
			dataArray[i][replaceFactor] = GetConVarFloat(dataArray[i][convarReplaceFactor]);
		}
		if (dataArray[i][convarReplaceClass] != INVALID_HANDLE)
		{
			decl String:buffer[ITEMCLASS_STRINGLENGTH];
			GetConVarString(dataArray[i][convarReplaceClass], buffer, sizeof(buffer));
			SetConVarString(dataArray[i][convarReplaceClass], buffer);
		}
		if (dataArray[i][convarWeaponModelReplacement] != INVALID_HANDLE)
		{
			GetConVarString(dataArray[i][convarWeaponModelReplacement], dataArray[i][weaponModelReplacement], WEAPON_MODEL_STRINGLENGTH-1);
		}
		
		if (dataArray[i][removalFactor] != FACTOR_NONE)
		{
			Debug_Print("removal factor: %f, replacement factor: %f, replacement class: %s", dataArray[i][removalFactor], dataArray[i][replaceFactor], dataArray[i][classNameReplacement]);
		}
		else
		{
			Debug_Print("removal limit: %i, replacement factor: %f, replacement class: %s", dataArray[i][removalLimit], dataArray[i][replaceFactor], dataArray[i][classNameReplacement]);
		}
		
		removalfactorcounter = FACTOR_NONE;
		replacefactorcounter = FACTOR_NONE;
		ent = INVALID_ENTITY;
		counter = 0;
		
		if (dataArray[i][aroundSpawn] != SPECIAL_OPTION_NO_VALUE)
		{
			adtItems = CreateArray();
			
			while ((ent = FindEntityByClassname2(ent, dataArray[i][className])) != INVALID_ENTITY)
			{
				PushArrayCell(adtItems, GetEntityDistanceFromPoint(ent, survivorPos));
			}
			new arraysize = GetArraySize(adtItems);
			if (!arraysize)
			{
				Debug_Print("AROUND_SPAWN option set, zero items found, continuing");
				i++;
				CloseHandle(adtItems);
				continue;
			}
			
			SortADTArray(adtItems, Sort_Ascending, Sort_Float);
			
			j = (arraysize < SAFEROOM_ITEMS_OF_A_KIND) ? arraysize-1 : SAFEROOM_ITEMS_OF_A_KIND-1;
			
			if (GetArrayCell(adtItems, j) > CHECKPOINT_ITEMWIPE_DISTANCE)
			{
				while (GetArrayCell(adtItems, j) > CHECKPOINT_ITEMWIPE_DISTANCE && j > 1)
				{
					j--;
				}
			}
			
			tempcompare = GetArrayCell(adtItems, j);
			
			if (tempcompare < CHECKPOINT_ITEMWIPE_DISTANCE)
			{
				if (FloatCompare(lowestItemDistance, 0.0) == 0)
					lowestItemDistance = tempcompare;
				
				Debug_Print("AROUND_SPAWN = %i, Lowest Item Distance to survivorPos: %f", dataArray[i][aroundSpawn], lowestItemDistance);
			}
			else if ((dataArray[i][aroundSpawn]) == SPECIAL_OPTION_YES)
			{
				Debug_Print("AROUND_SPAWN is YES, but no items within spawnrange, aborting");
				i++;
				CloseHandle(adtItems);
				continue;
			}
			
			CloseHandle(adtItems);
			
			ent = INVALID_ENTITY;
		
			if (dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO
			&& dataArray[i][removalLimit] != INVALID_ENTITY)
			{
				itemIndexes = CreateArray();
				j = 0;
				
				Debug_Print("REMOVAL_LIMIT option set to %i, counting items on map now", dataArray[i][removalLimit]);
				
				while ((ent = FindEntityByClassname2(ent, dataArray[i][className])) != INVALID_ENTITY)
				{
					if (GetEntityDistanceFromPoint(ent, survivorPos) > lowestItemDistance)
					{
						PushArrayCell(itemIndexes, ent);
					}
				}
				
				Debug_Print("REMOVAL_LIMIT: Found %i items on map", GetArraySize(itemIndexes));
				
				while (GetArraySize(itemIndexes) > dataArray[i][removalLimit])
				{
					j = GetRandomInt(0, GetArraySize(itemIndexes)-1);
					ent = GetArrayCell(itemIndexes, j);
					RemoveEdict(ent);
					RemoveFromArray(itemIndexes, j);
					
					Debug_Print("REMOVAL_LIMIT: Randomly deleted entity %i to reduce item count", ent);
				}
			
				CloseHandle(itemIndexes);
			}
		}
		
		ent = INVALID_ENTITY;
		prev = 0;
		
		while ((ent = FindEntityByClassname(ent, dataArray[i][className])) != INVALID_ENTITY)
		{
			if (prev)
			{
				nearSpawn = (GetEntityDistanceFromPoint(prev, survivorPos) <= lowestItemDistance);
				nearFinish = (endSaferoomExists && GetEntityDistanceFromPoint(prev, saferoomorigin) < CHECKPOINT_ITEMWIPE_DISTANCE);
				
				Debug_Print("ent %i, nearspawn = %b, nearfinish = %b", prev, nearSpawn, nearFinish);
				
				if (endSaferoomExists && scoringEnabled && nearFinish)
				{
					RemoveEdict(prev);
					Debug_Print("Removing %s of id %i near finishing Safe Area", dataArray[i][className], prev);
				}
				else if (dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO_VALUE
				|| (nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_YES)
				|| (nearSpawn && dataArray[i][removalLimit] != INVALID_ENTITY)
				|| (!nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO))
				{
					removalfactorcounter += dataArray[i][removalFactor];
					if (removalfactorcounter < FACTOR_FULL) // survives the removal
					{
						GetEntPropVector(prev, Prop_Send, VECTOR_ORIGIN_ENTPROP, positionArray[i][counter]); // save position and angles
						GetEntPropVector(prev, Prop_Send, ANGLE_ROTATION_ENTPROP, anglesArray[i][counter]);
						
						replacefactorcounter += dataArray[i][replaceFactor];
						if (replacefactorcounter >= FACTOR_FULL)
						{
							replaceItem[i][counter] = true;
							ReplaceItemWithAnother(i, prev, counter);
							Debug_Print("Indexed replacement %s of id %i as per replacement factor", dataArray[i][classNameReplacement], prev);
							replacefactorcounter -= FACTOR_FULL;
						}
						else
						{
							replaceItem[i][counter] = false;
							Debug_Print("Indexed original %s of id %i as per removal factor", dataArray[i][className], prev);
						}
						counter ++;
					}
					else
					{
						RemoveEdict(prev);
						Debug_Print("Removing %s of id %i as removal factor requested", dataArray[i][className], ent);
						removalfactorcounter -= FACTOR_FULL;
					}
				}
			}
			prev = ent;
		}
		
		if (prev)
		{
			nearSpawn = (GetEntityDistanceFromPoint(prev, survivorPos) <= lowestItemDistance);
			nearFinish = (endSaferoomExists && GetEntityDistanceFromPoint(prev, saferoomorigin) < CHECKPOINT_ITEMWIPE_DISTANCE);
			
			Debug_Print("ent %i, nearspawn = %b, nearfinish = %b", prev, nearSpawn, nearFinish);
			
			if (endSaferoomExists && scoringEnabled && nearFinish)
			{
				RemoveEdict(prev);
				Debug_Print("Removing %s of id %i near finishing Safe Area", dataArray[i][className], prev);
			}
			else if (dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO_VALUE
			|| (nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_YES)
			|| (!nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO))
			{
				removalfactorcounter += dataArray[i][removalFactor];
				if (removalfactorcounter < FACTOR_FULL) // survives the removal
				{
					GetEntPropVector(prev, Prop_Send, VECTOR_ORIGIN_ENTPROP, positionArray[i][counter]); // save position and angles
					GetEntPropVector(prev, Prop_Send, ANGLE_ROTATION_ENTPROP, anglesArray[i][counter]);
					
					replacefactorcounter += dataArray[i][replaceFactor];
					if (replacefactorcounter >= FACTOR_FULL)
					{
						replaceItem[i][counter] = true;
						ReplaceItemWithAnother(i, prev, counter);
						Debug_Print("Indexed replacement %s of id %i as per replacement factor", dataArray[i][classNameReplacement], prev);
						replacefactorcounter -= FACTOR_FULL;
					}
					else
					{
						replaceItem[i][counter] = false;
						Debug_Print("Indexed original %s of id %i as per removal factor", dataArray[i][className], prev);
					}
					counter ++;
				}
				else
				{
					RemoveEdict(prev);
					Debug_Print("Removing %s of id %i as removal factor requested", dataArray[i][className], ent);
					removalfactorcounter -= FACTOR_FULL;
				}
			}
		}
		
		counter ++;
		positionArray[i][counter][0] = FACTOR_NONE; //since we just overwrite the Index each map instead of wiping it, mark the 'end'
		
		i++;
	}
}

static WipeAllItems()
{
	Debug_Print("  ---Wiping all configured item spawns now to ensure identical spawns---  ");
	
	new i = 0;
	new ent = INVALID_ENTITY;
	new prev = 0;
	new bool:nearSpawn = false;
	
	do
	{
		Debug_Print("wiping %s now", dataArray[i][className]);
		ent = INVALID_ENTITY;
		prev = 0;
		
		while ((ent = FindEntityByClassname(ent, dataArray[i][className])) != INVALID_ENTITY)
		{
			if (prev)
			{
				if (dataArray[i][aroundSpawn] != SPECIAL_OPTION_NO_VALUE && dataArray[i][weaponIDReplacement] > 0)
				{
					nearSpawn = (GetEntityDistanceFromPoint(prev, survivorPos) < CHECKPOINT_ITEMWIPE_DISTANCE);
					
					if ((nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_YES)
					|| (!nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO))
					{
						RemoveEdict(prev);
						Debug_Print("Wiped entity %i of class %s", prev, dataArray[i][className]);
					}
				}
				else
				{
					RemoveEdict(prev);
					Debug_Print("Wiped entity %i of class %s", prev, dataArray[i][className]);
				}
			}
			prev = ent;
		}
		if (prev)
		{
			if (dataArray[i][aroundSpawn] != SPECIAL_OPTION_NO_VALUE && dataArray[i][weaponIDReplacement] > 0)
			{
				nearSpawn = (GetEntityDistanceFromPoint(prev, survivorPos) < CHECKPOINT_ITEMWIPE_DISTANCE);
				
				if ((nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_YES)
				|| (!nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO))
				{
					RemoveEdict(prev);
					Debug_Print("Wiped entity %i of class %s", prev, dataArray[i][className]);
				}
			}
			else
			{
				RemoveEdict(prev);
				Debug_Print("Wiped entity %i of class %s", prev, dataArray[i][className]);
			}
		}
		
		i++;
	}
	while (!StrEqual(dataArray[i][className], ""));
}

public Action:_IR_SpawnIndexedItems_Timer(Handle:timer)
{
	Debug_Print("  ---Spawning Items as saved in Index---  ");
	
	new counter = 0;
	new entID = INVALID_ENTITY;
	
	new i = 0;
	
	while (!StrEqual(dataArray[i][className], ""))
	{
		counter = 0;
		entID = INVALID_ENTITY;
		
		while (positionArray[i][counter][0] != FACTOR_NONE)
		{
			entID = replaceItem[i][counter] ? CreateEntityByName(dataArray[i][classNameReplacement]) : CreateEntityByName(dataArray[i][className]);
			if (entID == INVALID_ENTITY)
			{
				ThrowError("Failed to create %s entity", dataArray[i][className]);
			}
			else
			{
				Debug_Print("Spawned new entity %i from index, orig. class %s, replaced: %b, count %s", entID, dataArray[i][className], replaceItem[i][counter], dataArray[i][amountToReplace]);
			}
			
			if (dataArray[i][weaponModelReplacement][0] != 0)
			{
				SetEntityModel(entID, dataArray[i][weaponModelReplacement]);
			}
			
			if (dataArray[i][weaponIDReplacement] > 0)
			{
				SetEntProp(entID, Prop_Data, WEAPON_ID_ENTPROP, dataArray[i][weaponIDReplacement]);
			}
			
			TeleportEntity(entID, positionArray[i][counter], anglesArray[i][counter], NULL_VECTOR);

			DispatchKeyValue(entID, ENTITY_KEYVALUE_COUNT, dataArray[i][amountToReplace]);
			
			DispatchSpawn(entID);
		
			counter++;
		}
	
		i++;
	}
}

static GetAnySurvivorStartingPosition(Float:surPos[3])
{
	FOR_EACH_ALIVE_SURVIVOR(i)
	{
		//find any alive survivor's origin
		GetEntPropVector(i, Prop_Send, VECTOR_ORIGIN_ENTPROP, surPos);
		break;
	}
	if (!surPos[0])
	{
		Debug_Print("Did not find a survivor to calculate saferoom");
		return;
	}
}

static ReplaceItemWithAnother(i, entity, counter)
{
	if (entity != INVALID_ENTITY)
	{
		RemoveEdict(entity);
	}
	
	new replaceID = CreateEntityByName(dataArray[i][classNameReplacement]);
	if (replaceID == INVALID_ENTITY)
	{
		ThrowError("Failed to create %s entity", dataArray[i][classNameReplacement]);
	}
	
	if (dataArray[i][weaponModelReplacement][0] != 0)
	{
		SetEntityModel(replaceID, dataArray[i][weaponModelReplacement]);
	}
	
	if (dataArray[i][weaponIDReplacement] > 0)
	{
		SetEntProp(replaceID, Prop_Data, WEAPON_ID_ENTPROP, dataArray[i][weaponIDReplacement]);
	}
	
	TeleportEntity(replaceID, positionArray[i][counter], anglesArray[i][counter], NULL_VECTOR);

	if (StrEqual(dataArray[i][amountToReplace], ENTITY_COUNT_DEFAULT))
	{
		DispatchKeyValue(replaceID, ENTITY_KEYVALUE_COUNT, dataArray[i][amountToReplace]);
	}
	else
	{	
		new Handle:data = CreateDataPack();
		WritePackCell(data, replaceID);
		WritePackString(data, dataArray[i][amountToReplace]);
		CreateTimer(INDEX_RESPAWN_DELAY, _IR_DelayedItemMultiplier, data);
	}
	
	DispatchSpawn(replaceID);
	
	Debug_Print("Replaced Entity %i of class %s with %s %s, new ID %i", entity, dataArray[i][className], dataArray[i][amountToReplace], dataArray[i][classNameReplacement], replaceID);
}

public Action:_IR_DelayedItemMultiplier(Handle:timer, Handle:data)
{
	decl String:buffer[INTEGER_STRINGLENGTH];

	ResetPack(data);
	new ent = ReadPackCell(data);
	ReadPackString(data, buffer, sizeof(buffer));
	CloseHandle(data);
	
	DispatchKeyValue(ent, ENTITY_KEYVALUE_COUNT, buffer);
}

static HandleDynamicWeaponSpawns()
{
	new ent = INVALID_ENTITY;
	new prev = 0;
	new propid = 0;
	decl String:buffer[INTEGER_STRINGLENGTH];
	new i = 0;
	
	new bool:isFinale = L4D2_IsFinaleMapNow();
	new bool:nearSpawn = false;
	
	while ((ent = FindEntityByClassname(ent, ENTITY_SPAWN_WEAPON)) != INVALID_ENTITY)
	{
		if (prev)
		{
			propid = GetEntProp(prev, Prop_Data, WEAPON_ID_ENTPROP);

			IntToString(propid, buffer, sizeof(buffer));
			if (GetTrieValue(weaponDataTrie, buffer, i))
			{		
				if (dataArray[i][convarWeaponIDReplacement] != INVALID_HANDLE)
				{
					dataArray[i][weaponIDReplacement] = GetConVarInt(dataArray[i][convarWeaponIDReplacement]);
				}
				if (dataArray[i][convarWeaponModelReplacement] != INVALID_HANDLE)
				{
					GetConVarString(dataArray[i][convarWeaponModelReplacement], dataArray[i][weaponModelReplacement], WEAPON_MODEL_STRINGLENGTH-1);
				}
				
				if ((!(isFinale && dataArray[i][finaleOnly] == SPECIAL_OPTION_NO))
				&& (!(!isFinale && dataArray[i][finaleOnly] == SPECIAL_OPTION_YES)))
				{						
					if (dataArray[i][weaponIDReplacement] > 0)	
					{
						nearSpawn = (GetEntityDistanceFromPoint(prev, survivorPos) < CHECKPOINT_ITEMWIPE_DISTANCE);
						
						if (dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO_VALUE
						|| (nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_YES)
						|| (!nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO))
						{
							Debug_Print("Found weapon_spawn %i of id %i to be changed, new id %i replacement model %s", prev, propid, dataArray[i][weaponIDReplacement], dataArray[i][weaponModelReplacement]);
						
							SetEntProp(prev, Prop_Data, WEAPON_ID_ENTPROP, dataArray[i][weaponIDReplacement]);
							SetEntityModel(prev, dataArray[i][weaponModelReplacement]);
						}
					}
				}
			}
		}
		prev = ent;
	}
	if (prev)
	{
		propid = GetEntProp(prev, Prop_Data, WEAPON_ID_ENTPROP);
		
		IntToString(propid, buffer, sizeof(buffer));
		if (GetTrieValue(weaponDataTrie, buffer, i))
		{		
			if (dataArray[i][convarWeaponIDReplacement] != INVALID_HANDLE)
			{
				dataArray[i][weaponIDReplacement] = GetConVarInt(dataArray[i][convarWeaponIDReplacement]);
			}
			if (dataArray[i][convarWeaponModelReplacement] != INVALID_HANDLE)
			{
				GetConVarString(dataArray[i][convarWeaponModelReplacement], dataArray[i][weaponModelReplacement], WEAPON_MODEL_STRINGLENGTH-1);
			}
			
			if ((!(isFinale && dataArray[i][finaleOnly] == SPECIAL_OPTION_NO))
			&& (!(!isFinale && dataArray[i][finaleOnly] == SPECIAL_OPTION_YES)))
			{						
				if (dataArray[i][weaponIDReplacement] > 0)	
				{
					nearSpawn = (GetEntityDistanceFromPoint(prev, survivorPos) < CHECKPOINT_ITEMWIPE_DISTANCE);
					
					if (dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO_VALUE
					|| (nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_YES)
					|| (!nearSpawn && dataArray[i][aroundSpawn] == SPECIAL_OPTION_NO))
					{
						Debug_Print("Found weapon_spawn %i of id %i to be changed, new id %i replacement model %s", prev, propid, dataArray[i][weaponIDReplacement], dataArray[i][weaponModelReplacement]);
					
						SetEntProp(prev, Prop_Data, WEAPON_ID_ENTPROP, dataArray[i][weaponIDReplacement]);
						SetEntityModel(prev, dataArray[i][weaponModelReplacement]);
					}
				}
			}
		}
	}
	
	CheatCommand(_, "ent_fire", "ldq_launcher Kill"); // odd fix for strange GL in C5M5
}

static LoadKeyValues()
{
	BuildPath(Path_SM, configFile, sizeof(configFile), CONFIG_FILE_PATH);
	if(!FileExists(configFile)) 
	{
		SetFailState("%s cannot be read ... FATAL ERROR!", CONFIG_FILE_PATH);
	}
	
	if (weaponDataTrie != INVALID_HANDLE)
	{
		CloseHandle(weaponDataTrie);
	}
	weaponDataTrie = CreateTrie();
	
	if (keyValueHolder != INVALID_HANDLE)
	{
		CloseHandle(keyValueHolder);
	}
	keyValueHolder = CreateKeyValues(CONFIG_FILE_PATH);
	FileToKeyValues(keyValueHolder, configFile);
	KvRewind(keyValueHolder);
	
	if (KvGotoFirstSubKey(keyValueHolder))
	{
		new i = 0;
		new intvalue = 0;
		decl String:buffer[ITEMCLASS_STRINGLENGTH], String:buffer2[ITEMCLASS_STRINGLENGTH], Float:value;
		
		do
		{
			KvGetString(keyValueHolder, CONFIG_CLASSNAME, buffer, sizeof(buffer), "");
			if (StrEqual(buffer, ""))
			{
				KvGetString(keyValueHolder, CONFIG_WEAPON_ID, buffer, sizeof(buffer), "");
				if (StrEqual(buffer, ""))
				{
					SetFailState("%s cannnot be parsed ... Subkey without [classname] and [weapon_id] value!!", CONFIG_FILE_PATH);
				}
				else
				{
					SetTrieValue(weaponDataTrie, buffer, i);
					Debug_Print("Dataset %i, weapon_id %s read and saved in Trie", i, buffer);
				}
			}
			else
			{
				strcopy(dataArray[i][className], ITEMCLASS_STRINGLENGTH-1, buffer);
				Debug_Print("Dataset %i, classname %s read and saved", i, buffer);
				
				KvGetString(keyValueHolder, CONFIG_REMOVAL_LIMIT, buffer, sizeof(buffer), "");
				if (StrEqual(buffer, ""))
				{
					KvGetString(keyValueHolder, CONFIG_REMOVAL_FACTOR, buffer, sizeof(buffer), "0.0");
					value = StringToFloat(buffer);
					dataArray[i][removalFactor] = value;
					dataArray[i][removalLimit] = INVALID_ENTITY;
					Debug_Print("Dataset %i, removal factor %f read and saved", i, value);
					
					KvGetString(keyValueHolder, CONFIG_REMOVAL_CVAR, buffer2, sizeof(buffer2), "");
					if (!StrEqual(buffer2, ""))
					{
						dataArray[i][convarRemovalFactor] = CreateConVar(buffer2, buffer, " remove item by this factor ", SRS_CVAR_DEFAULT_FLAGS);
						Debug_Print("Dataset %i, remove convar %s with value %s read and created", i, buffer2, buffer);
					}
				}
				else
				{
					intvalue = StringToInt(buffer);
					dataArray[i][removalLimit] = intvalue;
					dataArray[i][removalFactor] = FACTOR_NONE;
					Debug_Print("Dataset %i, removal limit %i read and saved", i, intvalue);
					
					KvGetString(keyValueHolder, CONFIG_REMOVAL_LIMIT_CVAR, buffer2, sizeof(buffer2), "");
					if (!StrEqual(buffer2, ""))
					{
						dataArray[i][convarRemovalLimit] = CreateConVar(buffer2, buffer, " remove item to this limit ", SRS_CVAR_DEFAULT_FLAGS);
						Debug_Print("Dataset %i, remove limit convar %s with value %s read and created", i, buffer2, buffer);
					}
				}
				
				KvGetString(keyValueHolder, CONFIG_REPLACE_FACTOR, buffer, sizeof(buffer), "0.0");
				value = StringToFloat(buffer);
				dataArray[i][replaceFactor] = value;
				Debug_Print("Dataset %i, replace factor %f read and saved", i, value);
				
				KvGetString(keyValueHolder, CONFIG_REPLACE_CVAR, buffer2, sizeof(buffer2), "");
				if (!StrEqual(buffer2, ""))
				{
					dataArray[i][convarReplaceFactor] = CreateConVar(buffer2, buffer, " replace item by this factor ", SRS_CVAR_DEFAULT_FLAGS);
					Debug_Print("Dataset %i, replace convar %s with value %s read and created", i, buffer2, buffer);
				}
				
				KvGetString(keyValueHolder, CONFIG_REPLACE_CLASSNAME, buffer, sizeof(buffer), "");
				if (StrEqual(buffer, "") && !StrEqual(buffer2, ""))
				{
					SetFailState("%s parsing problem ... Replace Factor Convar %s specified, but no replacement class!!", CONFIG_FILE_PATH, buffer2);
				}
				else
				{
					strcopy(dataArray[i][classNameReplacement], ITEMCLASS_STRINGLENGTH-1, buffer);
					Debug_Print("Dataset %i, classNameReplacement %s read and saved", i, buffer);
				}
				
				KvGetString(keyValueHolder, CONFIG_REPLACE_CLASS_CVAR, buffer, sizeof(buffer), "");
				if (!StrEqual(buffer, ""))
				{
					dataArray[i][convarReplaceClass] = CreateConVar(buffer, dataArray[i][classNameReplacement], " replace item with item of this class ", SRS_CVAR_DEFAULT_FLAGS);
					Debug_Print("Dataset %i, replace class convar %s of value %s read and created", i, buffer, dataArray[i][classNameReplacement]);
				}
			}
			
			KvGetString(keyValueHolder, CONFIG_REPLACE_WEAPON_ID, buffer, sizeof(buffer), "0");
			intvalue = StringToInt(buffer);
			if (intvalue)
			{
				dataArray[i][weaponIDReplacement] = intvalue;
				Debug_Print("Dataset %i, weapon ID replacement %i read and saved", i, intvalue);
				
				KvGetString(keyValueHolder, CONFIG_REPLACE_WEAPON_ID_CVAR, buffer2, sizeof(buffer2), "");
				if (!StrEqual(buffer2, ""))
				{
					dataArray[i][convarWeaponIDReplacement] = CreateConVar(buffer2, buffer, " weapon ID of replacement weapon ", SRS_CVAR_DEFAULT_FLAGS);
					Debug_Print("Dataset %i, replacement id convar %s with value %s read and created", i, buffer2, buffer);
				}
			}
			else
			{
				dataArray[i][weaponIDReplacement] = 0;
			}
			
			KvGetString(keyValueHolder, CONFIG_REPLACE_MODEL, buffer, sizeof(buffer), "");
			if (StrEqual(buffer, "") && dataArray[i][weaponIDReplacement] > 0)
			{
				SetFailState("%s cannnot be parsed ... Subkey with replacement weapon id, but no model!", CONFIG_FILE_PATH);
			}
			strcopy(dataArray[i][weaponModelReplacement], WEAPON_MODEL_STRINGLENGTH-1, buffer);
			Debug_Print("Dataset %i, replacement model %s read and saved", i, buffer);
			
			KvGetString(keyValueHolder, CONFIG_REPLACE_MODEL_CVAR, buffer2, sizeof(buffer2), "");
			if (!StrEqual(buffer2, ""))
			{
				dataArray[i][convarWeaponModelReplacement] = CreateConVar(buffer2, buffer, " weapon model of replacement weapon ", SRS_CVAR_DEFAULT_FLAGS);
				Debug_Print("Dataset %i, replacement model convar %s with value %s read and created", i, buffer2, buffer);
			}
			
			
			KvGetString(keyValueHolder, CONFIG_ONLY_AROUND_SPAWN, buffer2, sizeof(buffer2), "");
			if (StrEqual(buffer2, "yes"))
			{
				dataArray[i][aroundSpawn] = SPECIAL_OPTION_YES;
				Debug_Print("Dataset %i, aroundSpawn setting %s read and set", i, buffer2);
			}
			else if (StrEqual(buffer2, "no"))
			{
				dataArray[i][aroundSpawn] = SPECIAL_OPTION_NO;
				Debug_Print("Dataset %i, aroundSpawn setting %s read and set", i, buffer2);
			}
			else
			{
				dataArray[i][aroundSpawn] = SPECIAL_OPTION_NO_VALUE;
			}
			
			KvGetString(keyValueHolder, CONFIG_FINALE_ONLY, buffer2, sizeof(buffer2), "");
			if (StrEqual(buffer2, CONFIG_SPECIAL_OPTION_YES))
			{
				dataArray[i][finaleOnly] = SPECIAL_OPTION_YES;
				Debug_Print("Dataset %i, finaleOnly setting %s read and set", i, buffer2);
			}
			else if (StrEqual(buffer2, CONFIG_SPECIAL_OPTION_NO))
			{
				dataArray[i][finaleOnly] = SPECIAL_OPTION_NO;
				Debug_Print("Dataset %i, finaleOnly setting %s read and set", i, buffer2);
			}
			else
			{
				dataArray[i][finaleOnly] = SPECIAL_OPTION_NO_VALUE;
			}
			
			KvGetString(keyValueHolder, CONFIG_AMOUNT_TO_SPAWN, buffer, sizeof(buffer), "");
			if (!StrEqual(buffer, ""))
			{
				strcopy(dataArray[i][amountToReplace], INTEGER_STRINGLENGTH-1, buffer);
				Debug_Print("Dataset %i, replace amount %s read and saved", i, buffer);
			}
			else
			{
				strcopy(dataArray[i][amountToReplace], INTEGER_STRINGLENGTH-1, ENTITY_COUNT_DEFAULT);
			}
			
			i++;
		}
		while (KvGotoNextKey(keyValueHolder));
	}
	else
	{
		SetFailState("%s cannnot be parsed ... No subkeys found!", CONFIG_FILE_PATH);
	}
}
#undef MODULE_NAME
